import { Scanner } from "./scanner"
import { Token, tokenize, TokenType } from "./token"

class Parser {
    private tokens: Array<Token>

    private index: number

    constructor(tokens: Array<Token>) {
        this.tokens = tokens
        this.index = 0
    }

    parse() {
        return this.jsonObject()
    }

    private peek() {
        return this.tokens[this.index]
    }

    private peekNext() {
        return this.tokens[this.index + 1]
    }

    private advance() {
        this.index++
        return this.tokens[this.index - 1]
    }

    private isAtEnd() {
        return this.index >= this.tokens.length
    }

    private jsonObject() {
        if (this.peek().tokenType === TokenType.LEFT_BRACE) {
            return this.getObject()
        } else {
            return this.getValue()
        }
    }

    private getValue() {
        if (this.peek().tokenType === TokenType.LEFT_BRACKET) {
            return this.getArray()
        } else {
            return this.getPrimaryValue()
        }
    }

    private getObject() {
        if (!this.matchToken(TokenType.LEFT_BRACE)) {
            throw new Error("Object begins with left brace {")
        }
        this.advance()
        const result: Record<string, any> = {}

        // 空对象情况
        if (this.matchToken(TokenType.RIGHT_BRACE)) {
            this.advance()
            return result
        }

        while (!this.isAtEnd() && !this.matchToken(TokenType.RIGHT_BRACE)) {
            const [key, value] = this.getPair()
            result[key] = value

            // 一个或最后一个元素, 接着是右花括号, 跳出循环
            if (this.matchToken(TokenType.RIGHT_BRACE)) {
                break
            }

            if (this.matchToken(TokenType.COMMA) && 
                this.peekNext().tokenType !== TokenType.RIGHT_BRACE) {
                    this.advance()
                } else {
                    throw new Error("Object Syntax error")
                }
        }

        if (!this.matchToken(TokenType.RIGHT_BRACE)) {
            throw new Error("Object should be closed with right brace")
        } else {
            this.advance()
        }

        return result
    }

    private getPair(): [string, any] {
        const key = this.getPrimaryValue() as string

        if (!this.matchToken(TokenType.SEMI_COLON)) {
            throw new Error("Semicolon should follow key")
        } else {
            this.advance()
        }

        const value = this.getPrimaryValue()

        return [key, value] 
    }

    private getArray() {
        if (!this.matchToken(TokenType.LEFT_BRACKET)) {
            throw new Error("Array begins with left bracket [")
        }
        this.advance()
        const result: any[] = []

        if (this.matchToken(TokenType.RIGHT_BRACKET)) {
            this.advance()
            return result
        }

        while (!this.isAtEnd() && this.peekNext().tokenType === TokenType.COMMA) {
            result.push(this.getPrimaryValue())
            this.advance()
        }

        if (this.isAtEnd()) {
            throw new Error("Syntax error: array has to be closed with right bracket")
        }

        result.push(this.getPrimaryValue())

        if (!this.matchToken(TokenType.RIGHT_BRACKET)) {
            throw new Error("Syntax error: array has to be closed with right bracket")
        } else {
            this.advance()
        }

        return result
    }

    private getPrimaryValue() {
        if (this.matchToken(TokenType.BOOLEAN)) {
            const token = this.advance()
            return token.value === 'true' ? true : false
        }

        if (this.matchToken(TokenType.NUMBER)) {
            const token = this.advance()
            return token.value.indexOf(".") === -1 ? parseInt(token.value) : parseFloat(token.value)
        }

        if (this.matchToken(TokenType.STRING)) {
            const token = this.advance()
            const result = token.value.substring(1, token.value.length - 1)
            return result
        }

        if (this.matchToken(TokenType.LEFT_BRACKET)) {
            return this.getArray()
        }

        if (this.matchToken(TokenType.LEFT_BRACE)) {
            return this.getObject()
        }

        throw new Error("Unknown TokenType. Please check the syntax")
    }

    private matchToken(tokenType: TokenType) {
        return this.peek().tokenType === tokenType
    }
}

export function parse(json: string) {
    const scanner = new Scanner(json)
    const tokens = tokenize(scanner)
    const parser = new Parser(tokens)
    return parser.parse()
}